package leetcod_300;

import java.util.*;

public class leetCode_17_LetterCombinationsofaPhoneNumber {
/*    时间复杂度：O(3M次方x4N次方)是对应三个字母的数字个数，N是对应四个字母的数字个数。
      空间复杂度：O(3M次方x4N次方)。*/

    //方法一
    //一个映射表，第二个位置是"abc“,第三个位置是"def"。。。
    //这里也可以用map，用数组可以更节省点内存
    String[] letter_map = {" ", "*", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    //最终输出结果的list
    List<String> res = new ArrayList<>();
    List<String> resTest = new ArrayList<>();

    List<String> letterCombinations1(String digits) {
        //注意边界条件
        if (digits == null || digits.length() == 0) {
            return new ArrayList<>();
        }
        iterStr(digits, new StringBuilder(), 0);
        return res;
    }

    //递归函数
    void iterStr(String str, StringBuilder letter, int index) {
        //递归的终止条件，注意这里的终止条件看上去跟动态演示图有些不同，主要是做了点优化
        //动态图中是每次截取字符串的一部分，"234"，变成"23"，再变成"3"，最后变成""，这样性能不佳
        //而用index记录每次遍历到字符串的位置，这样性能更好
        if (index == str.length()) {
            res.add(letter.toString());
            return;
        }
        //获取index位置的字符，假设输入的字符是"234"
        //第一次递归时index为0所以c=2，第二次index为1所以c=3，第三次c=4
        //subString每次都会生成新的字符串，而index则是取当前的一个字符，所以效率更高一点
        char c = str.charAt(index);
        //map_string的下表是从0开始一直到9， c-'0'就可以取到相对的数组下标位置
        //比如c=2时候，2-'0'，获取下标为2,letter_map[2]就是"abc"
        int pos = c - '0';
        String map_string = letter_map[pos];
        //遍历字符串，比如第一次得到的是2，页就是遍历"abc"
        for (int i = 0; i < map_string.length(); i++) {
            //调用下一层递归，用文字很难描述，请配合动态图理解
            letter.append(map_string.charAt(i));
            //如果是String类型做拼接效率会比较低
            //iterStr(str, letter+map_string.charAt(i), index+1);
            iterStr(str, letter, index + 1);
            //StringBuilder传入的都是同一个对象，所以在递归完成之后必须撤回上一次的操作，
            // 需要删除上一次添加的字符。而String每次改变之后传入的都是不同的对象。故无需撤销操作。
            //如果不删除，在for循环里，下一次运行时，letter数据就被污染了。（ad，ae）变成（ad，ade）
            letter.deleteCharAt(letter.length() - 1);
        }
    }

    //方法一练习：
    List<String> letterCombinationsTest1(String digits) {
        if (digits == null || digits.length() == 0) {
            return new ArrayList<>();
        }
        iterStrTest(digits, new StringBuilder(), 0);
        return resTest;
    }

    void iterStrTest(String str, StringBuilder letter, int index) {
        if (index == str.length()) {
            res.add(letter.toString());
            return;
        }
        char c = str.charAt(index);
        int pos = c - '0';
        String map_String = letter_map[pos];
        for (int i = 0; i < map_String.length(); i++) {
            letter.append(map_String.charAt(i));
            iterStrTest(str, letter, index);
            //StringBuilder传入的都是同一个对象，所以在递归完成之后必须撤回上一次的操作，
            // 需要删除上一次添加的字符。而String每次改变之后传入的都是不同的对象。故无需撤销操作。
            //如果不删除，在for循环里，下一次运行时，letter数据就被污染了。（ad，ae）变成（ad，ade）
            letter.deleteCharAt(letter.length() - 1);
        }


    }

    //方法二 队列
    public List<String> letterCombinations2(String digits) {
        List<String> combinations = new ArrayList<>();  //使用一个集合来存储所有的字母组合结果
        if (digits == null || digits.length() == 0) return combinations;

        //将号码字母对应关系存储进Map
        HashMap<Character, String[]> map = new HashMap<Character, String[]>() {{    //存储为数组更好操作
            put('2', new String[]{"a", "b", "c"});
            put('3', new String[]{"d", "e", "f"});
            put('4', new String[]{"g", "h", "i"});
            put('5', new String[]{"j", "k", "l"});
            put('6', new String[]{"m", "n", "o"});
            put('7', new String[]{"p", "q", "r", "s"});
            put('8', new String[]{"t", "u", "v"});
            put('9', new String[]{"w", "x", "y", "z"});
        }};

        //定义一个队列来存储所有的组合结果
        Queue<String> queue = new LinkedList<>();
        //遍历Digits，取到对应号码对应的字母数组
        for (int i = 0; i < digits.length(); i++) {
            queue_letterCombinations2(queue, map.get(digits.charAt(i)));
        }
        //要求返回List
        for (String s : queue) {
            combinations.add(s);
        }
        return combinations;
    }

    private Queue<String> queue_letterCombinations2(Queue<String> queue, String[] letters) {
        //Queue<String> queue = new LinkedList<String>();
        //初始定义的queue一定是空的，所以这时候把第一个号码的字母放入队列
        if (queue.size() == 0) {
            for (String letter : letters) {
                queue.add(letter);
            }
        } else {
            //对于后面到来字母，把queue出队列然后拼接以后进入队列
            int queueLength = queue.size(); //记录本次需要进行出列组合的元素数量
            for (int i = 0; i < queueLength; i++) {
                String s = queue.poll();    //队列头元素出队列
                for (String letter : letters) {
                    queue.add(s + letter);  //将出来的队列元素和电话号码对应的字母依次进行拼接并添加进队列
                }
            }
        }
        return queue;
    }

    //方法二练习
    private Queue<String> queue_letterCombinationsTest2(Queue<String> queue, String[] letters) {
        if (queue.size() == 0) {
            for (String letter : letters) {
                queue.add(letter);
            }
        } else {
            int queuelength = queue.size();
            for (int i = 0; i < queuelength; i++) {
                String s = queue.poll();
                for (String letter : letters) {
                    queue.add(s + letter);
                }
            }
        }
        return queue;
    }

    public List<String> letterCombinationsTest2(String digits) {
        List<String> combinations = new ArrayList<>();
        if (digits == null || digits.length() == 0) return combinations;
        HashMap<Character, String[]> map = new HashMap<Character, String[]>() {{
            put('2', new String[]{"a", "b", "c"});
            put('3', new String[]{"d", "e", "f"});
            put('4', new String[]{"g", "h", "i"});
            put('5', new String[]{"j", "k", "l"});
            put('6', new String[]{"m", "n", "o"});
            put('7', new String[]{"p", "q", "r", "s"});
            put('8', new String[]{"t", "u", "v"});
            put('9', new String[]{"w", "x", "y", "z"});
        }};

        Queue<String> queue = new LinkedList<>();
        for (int i = 0; i < digits.length(); i++) {
            queue_letterCombinationsTest2(queue, map.get(digits.charAt(i)));
        }
        for (String s : queue) {
            combinations.add(s);
        }
        return combinations;
    }

}
